//! 副作用函数  依赖收集

import {isArray, isInteger} from "./utils";
import {TriggerOrAdd} from "./operators";


// 给effect 做一个标识
let uid = 0;
// 存储当前的effect
let activeEffect;
const effectStack = []; //定义一个栈
function createReactiveEffect(fn,options) {
   const effect = function reactiveEffect() {
      // 如果effect已经在栈里了 就不要再加入到栈
      if (!effectStack.includes(effect)) {
         try {
            effectStack.push(effect);// 入栈处理，利用的是：函数调用是一个栈
            activeEffect = effect; // 把当前的effect赋值给activeEffect方便让track方法获取
            // 一进来就先实行fn() fn函数取值时 会执行get方法
            fn();
         }finally {
            // 出栈处理 pop()方法把数组的最后一个元素删除 shift()方法把数组中的第一个元素删除
            effectStack.pop();
            // activeEffect 重置一下  它时effectStack这个栈的最后一个
            activeEffect = effectStack[effectStack.length - 1];
         }
      }
   }
   effect.id = uid++; // 制作一个effect标识，用于区分effect
   effect._isEffect = true; // 标识这个是响应式的 effect
   effect.raw = fn; // 保存effect原本的函数是fn
   effect.options = options; // 保存effect原本的入参是options

   return effect;
}

// 依赖收集方法
// 让某个对象中的属性，收集当前对应的effect函数
// 依赖收集方法 意思：我要对target这个对象的key属性进行取值（TrackGet）的时候进行收集
const targetMap = new WeakMap();
export function track(target,type,key) {
   //  activeEffect 拿到当前的effect  就是当前正在运行的 effect
   // 考虑：如果一个对象{name：’xx‘，age：12} 的name对应多个effect 【effect1，effect2】
   // weakMap =》 key是：{name：’xx‘，age：12} value：Map => key：name，value：set(【effect1，effect2】）
   if (activeEffect === undefined) { // 此属性不用收集依赖，因为没有在effect中使用
      return false;
   }
   // 拿出这个对象
   let depsMap = targetMap.get(target);
   if (!depsMap) { // 如果没有。就往targetMap里面放
      targetMap.set(target,(depsMap = new Map()));
   }
   // 然后再找：depMap里面有没有对应的set()
   let dep = depsMap.get(key);
   if (!dep) { // 如果没有
      depsMap.set(key,(dep = new Set()));
   }
   // 再看下set里面有没有effect 如果没有  就添加一个当前的effect
   if (!dep.has(activeEffect)) {
      dep.add(activeEffect);
   }
}

//! 触发更新 找属性对应的effect 让它执行
export function trigger(target,type,key,newValue=undefined,oldValue=undefined) {
   // 首先：如果这个属性没有收集过effect 就不需要做任何操作
   let depsMap = targetMap.get(target);
   if (!depsMap) return false; // 没有收集

   // 我们要将所有的 要执行的effect 全部存到一个新的集合中  最终一起执行
   const effects = new Set(); // 这里就已经去重了

   const add = (effectsToAdd) => {
      if (effectsToAdd) {
         effectsToAdd.forEach(effect => effects.add(effect));
      }
   }
   // 1.看修改的是不是数组的长度，因为影响比较大 ：
   // 如arr[2] 没改前是3 我这样修改 arr.length = 1;数组的长度变了，arr[2] 就是undefined了，后面没了
   if (key === 'length' && isArray(target)) {
      // 如果对应的长度 有依赖收集需要更新
      depsMap.forEach((dep,key) => {
         // key > newValue 如果你改的长度比收集的索引小 那么对应的effect也要执行
         if (key === 'length' || key > newValue) {
            add(dep);
         }
      });
   }else { // 可能是对象
      if (key !== undefined) { // 这里肯定是修改，不能是新增
         add(depsMap.get(key)); // depsMap.get(key) 如果effect中没有收集 就不管
      }
      // 如果修改数组中的某一个索引 怎么办： 如arr[100] = 1;
      switch (type) {
         case TriggerOrAdd: // 新增操作
            // 如果是数组  并且改索引
            if (isArray(target) && isInteger(key)) {
               // 如果effect收集了length 就触发length 的更新
               add(depsMap.get('length'));
            }
      }
   }


   //! 最终 我们要让开始存的effects 执行一下  触发更新一下
   effects.forEach(effect => effect());
}

export function effect(fn, options={}) {
   // 我需要让这个effect变成响应式的effect，可以做到数据变化，重新执行
   // 创建一个响应式effect
   // 返回的effect是一个函数
   const effect = createReactiveEffect(fn,options);
   // 如果不是懒执行
   if (!options.lazy) {
      effect();// 响应式effect 默认先执行一次
   }

   return effect;
}
